package com.tally.framework.security.config;

import com.tally.project.tool.token.filter.TokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.annotation.Resource;

/**
 * @Author soul
 * @Date 2023/5/14
 * @Description SpringSecurity安全框架配置
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /** 认证成功结果处理器 */
    @Resource
    private AuthenticationSuccessHandler loginSuccessHandler;
    /** 认证失败结果处理器 */
    @Resource
    private AuthenticationFailureHandler loginFailureHandler;
    /** 认证过期结果处理器 */
    @Resource
    private AuthenticationEntryPoint authenticationEntryPoint;
    /** 登出成功结果处理器 */
    @Resource
    private LogoutSuccessHandler logoutSuccessHandler;

    /** 会话信息过期处理器 */
    @Resource
    private InvalidSessionStrategy invalidSessionStrategy;
    /** 会话信息结束处理器 */
    @Resource
    private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;

    @Qualifier("detailsServiceImpl")
    @Autowired
    private UserDetailsService userDetailsService;

    @Resource
    private TokenFilter tokenFilter;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * http请求拦截配置
     * @param http
     * @throws Exception
     */
    protected void configure(HttpSecurity http) throws Exception {
        // 允许跨域
//                http.cors().configurationSource(this.corsConfigurationSource());
        // 1、配置权限认证
        http
                .authorizeRequests()
                // 配置不拦截路由
                .antMatchers("/captcha", "/captcha/**", "/system/user/forget", "/system/user/register", "/tool/swagger", "/swagger-ui/index.html", "redirect:/swagger-ui/index.html", "/swagger-ui/**", "/swagger-resources/**", "/webjars/**", "/v3/**", "/api/**").permitAll()
                // 任何其它请求都需要身份认证
                .anyRequest().authenticated()
                .and()
                // 认证过期结果处理器
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
                // 3、注销
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll()
                .and()
                // 4、禁用跨站csrf攻击防御
                .csrf().disable();

        // 5、Session 会话管理配置
        http.sessionManagement()
                // 配置使用自定义的 Session 会话失效处理策略
//                        .invalidSessionStrategy(invalidSessionStrategy)
                // 设置单用户的 Session 最大并发会话数量，-1 表示不受限制
                .maximumSessions(1)
                // 设置为 true，表示某用户达到最大会话并发数后，新会话请求会被拒绝登录；
                // 设置为 false，表示某用户达到最大会话并发数后，新会话请求访问时，其最老会话会在下一次请求时失效
                .maxSessionsPreventsLogin(false)
                // 设置所要使用的 sessionRegistry，默认为 SessionRegistryImpl 实现类
                .sessionRegistry(sessionRegistry())
                // 最老会话在下一次请求时失效，并按照自定义策略处理
                .expiredSessionStrategy(sessionInformationExpiredStrategy);

        // 用自定义的 LoginFilter 实例代替 UsernamePasswordAuthenticationFilter
        http.addFilterBefore(loginFilter(), UsernamePasswordAuthenticationFilter.class);

        http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * 配置Json登陆方式
     * @return
     * @throws Exception
     */
    @Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setFilterProcessesUrl("/user/login");
        loginFilter.setUsernameParameter("username");
        loginFilter.setPasswordParameter("password");
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setAuthenticationSuccessHandler(loginSuccessHandler);
        loginFilter.setAuthenticationFailureHandler(loginFailureHandler);
        return loginFilter;
    }

    /**
     * 跨域配置
     * @return
     */
    private CorsConfigurationSource corsConfigurationSource(){
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        // 这个得加上，一些复杂的请求方式会带有header，不加上跨域会失效。
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addExposedHeader("*");
        corsConfiguration.addAllowedOriginPattern("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",corsConfiguration);
        return source;
    }

    /**
     * 注册 SessionRegistry，该 Bean 用于管理 Session 会话并发控制
     */
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

    /**
     * 指定加密方式
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        // 使用BCrypt加密密码
        return new BCryptPasswordEncoder();
    }

}
